package hu.mapro.model.roo.proxy;

import static hu.mapro.model.roo.MaproModelJavaType.ROO_MAPRO_PROXY;
import static org.springframework.roo.addon.gwt.GwtJavaType.PROXY_FOR;
import static org.springframework.roo.addon.gwt.GwtJavaType.PROXY_FOR_NAME;
import hu.mapro.model.roo.MaproModelJavaType;
import hu.mapro.model.roo.MaproModelUtil;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.addon.gwt.GwtFileManager;
import org.springframework.roo.addon.gwt.GwtUtils;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeIdentifier;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.details.BeanInfoUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetailsBuilder;
import org.springframework.roo.classpath.details.ConstructorMetadataBuilder;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.metadata.AbstractHashCodeTrackingMetadataNotifier;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
import org.springframework.roo.metadata.MetadataItem;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.RooJavaType;
import org.springframework.roo.project.LogicalPath;
import org.springframework.roo.project.ProjectOperations;

@Component(immediate = true)
@Service
public class MaproModelProxyMetadataProviderImpl extends
        AbstractHashCodeTrackingMetadataNotifier implements
        MaproModelProxyMetadataProvider {

    @Reference protected GwtFileManager gwtFileManager;
    @Reference protected ProjectOperations projectOperations;
    @Reference protected TypeLocationService typeLocationService;

    protected void activate(final ComponentContext context) {
        metadataDependencyRegistry.registerDependency(
                PhysicalTypeIdentifier.getMetadataIdentiferType(),
                getProvidesType());
    }

    protected void deactivate(final ComponentContext context) {
        metadataDependencyRegistry.deregisterDependency(
                PhysicalTypeIdentifier.getMetadataIdentiferType(),
                getProvidesType());
    }

    public MetadataItem get(final String metadataIdentificationString) {
        final ClassOrInterfaceTypeDetails proxy = getGovernor(metadataIdentificationString);
        if (proxy == null) {
            return null;
        }

        final AnnotationMetadata mirrorAnnotation = MemberFindingUtils
                .getAnnotationOfType(proxy.getAnnotations(),
                        MaproModelJavaType.ROO_MAPRO_PROXY);
        if (mirrorAnnotation != null) {
	        Set<ClassOrInterfaceTypeDetails> allProxies = typeLocationService
	                .findClassesOrInterfaceDetailsWithAnnotation(PROXY_FOR, PROXY_FOR_NAME);
	        
	        
			ClassOrInterfaceTypeDetailsBuilder cidBuilder = new ClassOrInterfaceTypeDetailsBuilder(proxy.getDeclaredByMetadataId());
			cidBuilder.setName(proxy.getName());
			
            for (final ClassOrInterfaceTypeDetails proxyCid : typeLocationService
                    .findClassesOrInterfaceDetailsWithAnnotation(MaproModelJavaType.ROO_MAPRO_PROXY_ACCESSOR)) {

            	buildProxyMetaClass(
            			cidBuilder,
            			proxyCid.getName(),
            			allProxies
    			);
	        
            	String ptc = gwtFileManager.write(cidBuilder.build(),
            			GwtUtils.PROXY_REQUEST_WARNING);
            	
            	final MaproModelProxyMetadata metadata = new MaproModelProxyMetadata(
            			metadataIdentificationString, ptc);
            	notifyIfRequired(metadata);
            	
            	return metadata;
            }
            
        }
        
        final AnnotationMetadata mirrorAnnotation2 = MemberFindingUtils
                .getAnnotationOfType(proxy.getAnnotations(),
                        MaproModelJavaType.ROO_MAPRO_PROXY_ACCESSOR);
        if (mirrorAnnotation2 != null) {
	        Set<ClassOrInterfaceTypeDetails> allProxies = typeLocationService
	                .findClassesOrInterfaceDetailsWithAnnotation(PROXY_FOR, PROXY_FOR_NAME);
	        
	        
			ClassOrInterfaceTypeDetailsBuilder fieldsClass = new ClassOrInterfaceTypeDetailsBuilder(proxy.getDeclaredByMetadataId());
			fieldsClass.setName(proxy.getName());
			
	        
	        buildProxyMetaAccessorClass(
	        		fieldsClass,
	        		allProxies
			);
	        
	        String ptc2 = gwtFileManager.write(fieldsClass.build(),
	        		GwtUtils.PROXY_REQUEST_WARNING);
	        
	        final MaproModelProxyMetadata metadata2 = new MaproModelProxyMetadata(
	        		metadataIdentificationString, ptc2);
	        notifyIfRequired(metadata2);
	        return metadata2;
        }
     
        return null;
        
    }

	void buildProxyMetaClass(
			ClassOrInterfaceTypeDetailsBuilder cidBuilder,
			JavaType proxyFieldsName,
			Set<ClassOrInterfaceTypeDetails> allProxies
	) {
		Set<JavaType> domain = new HashSet<JavaType>();
		for (ClassOrInterfaceTypeDetails d : allProxies) {
			domain.add(d.getType());
		}

		final String metadataIdentificationString = cidBuilder.getDeclaredByMetadataId();
		
        MaproModelUtil.initProxy(cidBuilder);

        for (ClassOrInterfaceTypeDetails gwtProxy : allProxies) {
        	FieldMetadataBuilder field = new FieldMetadataBuilder(metadataIdentificationString);
        	
        	final JavaType mirroredType = GwtUtils.lookupProxyTargetType(gwtProxy);
        	
        	JavaType proxyTypeName = gwtProxy.getName();

        	JavaType routeName = createMetaType(proxyTypeName, true);

        	String innerMetaId = PhysicalTypeIdentifier.createIdentifier(routeName, PhysicalTypeIdentifier.getPath(metadataIdentificationString));
        	ClassOrInterfaceTypeDetailsBuilder innerType = new ClassOrInterfaceTypeDetailsBuilder(innerMetaId);
        	innerType.setPhysicalTypeCategory(PhysicalTypeCategory.CLASS);
			innerType.setName(routeName);
			innerType.setModifier(Modifier.PUBLIC);

        	JavaType tRef = new JavaType(
					"java.lang.Object",
					0,
					DataType.TYPE,
					new JavaSymbolName("T"),
					null
			);
        	JavaType rRef = new JavaType(
					"java.lang.Object",
					0,
					DataType.TYPE,
					new JavaSymbolName("R"),
					null
			);
        	JavaType nRef = new JavaType(
					"java.lang.Object",
					0,
					DataType.TYPE,
					JavaType.WILDCARD_NEITHER,
					null
			);
			
			List<JavaType> extendsArgs = new ArrayList<JavaType>();
			extendsArgs.add(rRef);
			extendsArgs.add(tRef);
			extendsArgs.add(gwtProxy.getName());
			
			
			JavaType metaBase = new JavaType(
					MaproModelJavaType.ENTITY_BASE.getFullyQualifiedTypeName(),
					0,
					DataType.TYPE,
					null,
					extendsArgs
			);
			List<JavaType> extendsTypes = Collections.singletonList(metaBase);
			innerType.setExtendsTypes(extendsTypes);
			
			ConstructorMetadataBuilder defC = new ConstructorMetadataBuilder(innerType.getDeclaredByMetadataId());
			innerType.addConstructor(defC);

			ConstructorMetadataBuilder def2 = new ConstructorMetadataBuilder(innerType.getDeclaredByMetadataId());
			
			
			JavaType metaRoot = new JavaType(
					MaproModelJavaType.ACCESSIBLE_PROPERTY.getFullyQualifiedTypeName(),
					0,
					DataType.TYPE,
					null,
					Arrays.asList(rRef, nRef, tRef)
			);
			def2.addParameter("route", metaRoot);
			JavaType metaProp = new JavaType(
					MaproModelJavaType.PROPERTY.getFullyQualifiedTypeName(),
					0,
					DataType.TYPE,
					null,
					Arrays.asList(tRef, proxyTypeName)
			);
			def2.addParameter("property", metaProp);
			InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
			bodyBuilder.append("super(route, property);");
			def2.setBodyBuilder(bodyBuilder);
			innerType.addConstructor(def2);
			
			for(MethodMetadata m : gwtProxy.getDeclaredMethods()) {
				if (isAccessorMethod(m, gwtProxy)) {
					JavaType type = m.getReturnType();
					
					boolean isDomain = domain.contains(type);
					
					JavaSymbolName propertyName = BeanInfoUtils.getPropertyNameForJavaBeanMethod(m);
					
					MethodMetadataBuilder metaMethod = new MethodMetadataBuilder(innerType.getDeclaredByMetadataId());
					String propertyNameString = StringUtils.uncapitalize(propertyName.getSymbolName());
					metaMethod.setMethodName(new JavaSymbolName(propertyNameString));
					metaMethod.setModifier(Modifier.PUBLIC);
					JavaType targetMetaType;
					if (isDomain) {
						targetMetaType = new JavaType(
								type.getSimpleTypeName()+"Meta",
								0,
								DataType.TYPE,
								null,
								Arrays.asList(rRef, proxyTypeName)
						);
					} else {
						JavaType tDef = new JavaType(
								"java.lang.Object",
								0,
								DataType.TYPE,
								new JavaSymbolName("T"),
								null
						);
						List<JavaType> classArgs = new ArrayList<JavaType>();
						classArgs.add(tDef);
						classArgs.add(type);
						String metaTypeName = MaproModelJavaType.VALUE_BASE.getFullyQualifiedTypeName();
						if (JdkJavaType.SET.getFullyQualifiedTypeName().equals(type.getFullyQualifiedTypeName())) {
							metaTypeName = MaproModelJavaType.ENTITY_BASE.getFullyQualifiedTypeName();
						}
						targetMetaType = new JavaType(
								metaTypeName,
								0,
								DataType.TYPE,
								null,
								Arrays.asList(rRef, proxyTypeName, type)
								
						);
					}
					metaMethod.setReturnType(targetMetaType);
					InvocableMemberBodyBuilder metaBodyBuilder = new InvocableMemberBodyBuilder();
					metaBodyBuilder.appendFormalLine("return new "+targetMetaType.getNameIncludingTypeParameters()+"(this, "+proxyFieldsName.getSimpleTypeName()+"."+mirroredType.getSimpleTypeName()+"."+propertyNameString+");");
					
					metaMethod.setBodyBuilder(metaBodyBuilder);
					
					innerType.addMethod(metaMethod);
					
				}
			}
			
        	cidBuilder.addInnerType(innerType);
        	
        	
        	field.setFieldName(new JavaSymbolName(StringUtils.uncapitalize(mirroredType.getSimpleTypeName())));
        	JavaType fieldType = new JavaType(
        			innerType.getName().getSimpleTypeName(), 
        			0,
        			DataType.TYPE,
        			null,
        			Arrays.asList(proxyTypeName, proxyTypeName)
        	);
        	
			field.setFieldType(fieldType);
        	
        	field.setFieldInitializer("new " + fieldType.getNameIncludingTypeParameters() + "()");
        	
        	cidBuilder.addField(field);
        }
        
	}

	void buildProxyMetaAccessorClass(
			ClassOrInterfaceTypeDetailsBuilder fieldsClass,
			Set<ClassOrInterfaceTypeDetails> allProxies
	) {

        MaproModelUtil.initProxyAccessor(fieldsClass);
		
		for (ClassOrInterfaceTypeDetails gwtProxy : allProxies) {
			
			final JavaType mirroredType = GwtUtils.lookupProxyTargetType(gwtProxy);
			
			
			ClassOrInterfaceTypeDetails mirroredTypeDetails = null;
			if (typeLocationService!=null) {
				mirroredTypeDetails = typeLocationService.getTypeDetails(mirroredType);
			}
			
			JavaType proxyTypeName = gwtProxy.getName();
			
			JavaType tRef = new JavaType(
					"java.lang.Object",
					0,
					DataType.TYPE,
					new JavaSymbolName("T"),
					null
					);
			
			List<JavaType> extendsArgs = new ArrayList<JavaType>();
			extendsArgs.add(tRef);
			extendsArgs.add(gwtProxy.getName());
			
			
			JavaType proxyFieldsName = new JavaType(
					mirroredType.getSimpleTypeName(),
					fieldsClass.getName()
					);
			String proxyFieldsMetaId = PhysicalTypeIdentifier.createIdentifier(proxyFieldsName, PhysicalTypeIdentifier.getPath(fieldsClass.getDeclaredByMetadataId()));
			ClassOrInterfaceTypeDetailsBuilder proxyFieldsClass = new ClassOrInterfaceTypeDetailsBuilder(proxyFieldsMetaId);
			proxyFieldsClass.setPhysicalTypeCategory(PhysicalTypeCategory.INTERFACE);
			proxyFieldsClass.setName(proxyFieldsName);
			proxyFieldsClass.setModifier(Modifier.PUBLIC|Modifier.STATIC);
			fieldsClass.addInnerType(proxyFieldsClass);
			
			for(MethodMetadata m : gwtProxy.getDeclaredMethods()) {
				if (isAccessorMethod(m, gwtProxy)) {
					JavaType type = m.getReturnType();
					
					JavaSymbolName propertyName = BeanInfoUtils.getPropertyNameForJavaBeanMethod(m);
					
					MethodMetadata mutatorMethod = null;
					
					for (MethodMetadata mut : gwtProxy.getDeclaredMethods()) {
						if (isMutatorMethod(mut, gwtProxy) &&
								BeanInfoUtils.getPropertyNameForJavaBeanMethod(mut).equals(propertyName)) {
							mutatorMethod = mut;
							break;
						}
					}
					
					boolean readOnly = mutatorMethod == null;


					String propertyNameString = StringUtils.uncapitalize(propertyName.getSymbolName());
					JavaSymbolName fieldNameSymbol = new JavaSymbolName(propertyNameString);
					
					String label = propertyName.getReadableSymbolName();
					
					if (mirroredTypeDetails!=null) {
						FieldMetadata mirroredField = mirroredTypeDetails.getDeclaredField(fieldNameSymbol);
						if (mirroredField!=null) {
							AnnotationMetadata labelAnnotation = mirroredField.getAnnotation(MaproModelJavaType.ROO_MAPRO_LABEL);
							if (labelAnnotation!=null) {
								AnnotationAttributeValue<String> labelAnnotationValue = labelAnnotation.getAttribute("value");
								if (labelAnnotationValue!=null) {
									label = labelAnnotationValue.getValue();
								}
							}
						}
					}
					
					
					List<JavaType> classArgOne = new ArrayList<JavaType>();
					classArgOne.add(proxyTypeName);
					List<JavaType> classArgsTwo = new ArrayList<JavaType>();
					classArgsTwo.add(proxyTypeName);
					classArgsTwo.add(type);

					JavaType typeType = MaproModelJavaType.PROPERTY;
					List<JavaType> typeArgs = classArgsTwo;
					JavaType objectType = MaproModelJavaType.ABSTRACT_PROPERTY;
					List<JavaType> objectArgs = classArgsTwo;
					List<String> constructorArgs = new ArrayList<String>();
					constructorArgs.add("\""+propertyNameString+"\"");
					constructorArgs.add("\""+StringEscapeUtils.escapeJava(label)+"\"");
					
					if (JdkJavaType.SET.getFullyQualifiedTypeName().equals(type.getFullyQualifiedTypeName())) {
						typeType = MaproModelJavaType.ABSTRACT_SET_PROPERTY;
						objectType = MaproModelJavaType.ABSTRACT_SET_PROPERTY;
						objectArgs = new ArrayList<JavaType>();
						objectArgs.add(proxyTypeName);
						objectArgs.add(type.getParameters().get(0));
					} else if (type.equals(JavaType.STRING)) {
						typeType = MaproModelJavaType.DISPLAYABLE_PROPERTY;
						objectType = MaproModelJavaType.STRING_PROPERTY;
						objectArgs = classArgOne;
					} else if (type.equals(JavaType.BOOLEAN_OBJECT)) {
						typeType = MaproModelJavaType.DISPLAYABLE_PROPERTY;
						objectType = MaproModelJavaType.BOOLEAN_PROPERTY;
						objectArgs = classArgOne;
					} else if (type.equals(JdkJavaType.DATE)) {
						typeType = MaproModelJavaType.DISPLAYABLE_PROPERTY;
						objectType = MaproModelJavaType.DATE_PROPERTY;
						objectArgs = classArgOne;
					} else if (
							type.equals(JavaType.DOUBLE_OBJECT) ||
							type.equals(JavaType.DOUBLE_PRIMITIVE) ||
							type.equals(JavaType.FLOAT_OBJECT) ||
							type.equals(JavaType.FLOAT_PRIMITIVE) ||
							type.equals(JavaType.INT_OBJECT) ||
							type.equals(JavaType.INT_PRIMITIVE) ||
							type.equals(JavaType.LONG_OBJECT) ||
							type.equals(JavaType.LONG_PRIMITIVE) ||
							type.equals(JavaType.BYTE_OBJECT) ||
							type.equals(JavaType.BYTE_PRIMITIVE) ||
							type.equals(JavaType.SHORT_OBJECT) ||
							type.equals(JavaType.SHORT_PRIMITIVE)
					) {
						typeType = MaproModelJavaType.DISPLAYABLE_PROPERTY;
						objectType = MaproModelJavaType.NUMBER_PROPERTY;
						objectArgs = classArgsTwo;
					} else if (typeLocationService != null) {
						
						ClassOrInterfaceTypeDetails td = typeLocationService.getTypeDetails(type);
						
						if (td!=null) {
							if (td.getPhysicalTypeCategory()==PhysicalTypeCategory.ENUMERATION) {
								objectType = MaproModelJavaType.ENUM_PROPERTY;
								objectArgs = classArgsTwo;
								constructorArgs.add("new hu.mapro.gwtui.client.model.ToStringLabelProvider<"+type.getFullyQualifiedTypeName()+">()");
							}
						}
					}
						
					
					JavaType targetTypeType = new JavaType(
							typeType.getFullyQualifiedTypeName(),
							0,
							DataType.TYPE,
							null,
							typeArgs
							);
					JavaType targetObjectType = new JavaType(
							objectType.getFullyQualifiedTypeName(),
							0,
							DataType.TYPE,
							null,
							objectArgs
							);
					
					// ROO bug? workaround
					targetTypeType = targetObjectType;
					
					
					
					FieldMetadataBuilder metaAccessorField = new FieldMetadataBuilder(proxyFieldsClass.getDeclaredByMetadataId());
					metaAccessorField.setFieldName(fieldNameSymbol);
					metaAccessorField.setModifier(Modifier.PUBLIC|Modifier.STATIC|Modifier.FINAL);
					metaAccessorField.setFieldType(targetTypeType);
					InvocableMemberBodyBuilder metaBodyBuilder = new InvocableMemberBodyBuilder();
					metaBodyBuilder.appendFormalLine("@Override");
					metaBodyBuilder.appendFormalLine("public "+type.getNameIncludingTypeParameters()+" get("+proxyTypeName.getFullyQualifiedTypeName()+" object) {");
					metaBodyBuilder.appendFormalLine("return object."+m.getMethodName().getSymbolName()+"();");
					metaBodyBuilder.appendFormalLine("}");
					
					if (!readOnly) {
						metaBodyBuilder.appendFormalLine("	@Override");
						metaBodyBuilder.appendFormalLine("	public void set("+proxyTypeName.getFullyQualifiedTypeName()+" object, "+type.getNameIncludingTypeParameters()+" value) {");
						metaBodyBuilder.appendFormalLine("		object."+mutatorMethod.getMethodName().getSymbolName()+"(value);");
						metaBodyBuilder.appendFormalLine("	}");
					}
					
					
					metaAccessorField.setFieldInitializer(
							"new "+targetObjectType.getNameIncludingTypeParameters()+
							"("+StringUtils.join(constructorArgs, ",")+")"+
							" {"+
							metaBodyBuilder.getOutput()+		
							"}"
							
							);
					
					proxyFieldsClass.addField(metaAccessorField);
					
					
				}
			}
			
		}
		
	}
	
	private JavaType createMetaType(JavaType proxyTypeName, final boolean forClass) {
		JavaType tDef = new JavaType(
				"java.lang.Object",
				0,
				DataType.TYPE,
				new JavaSymbolName("T"),
				null
		) {
			@Override
			public String toString() {
				if (forClass) {
					return getSimpleTypeName();
				} else {
					return super.toString();
				}
			}
		};
		JavaType rDef = new JavaType(
				"java.lang.Object",
				0,
				DataType.TYPE,
				new JavaSymbolName("R"),
				null
		) {
			@Override
			public String toString() {
				if (forClass) {
					return getSimpleTypeName();
				} else {
					return super.toString();
				}
			}
		};
		List<JavaType> classArgs = new ArrayList<JavaType>();
		classArgs.add(rDef);
		classArgs.add(tDef);
		JavaType routeName = new JavaType(
				proxyTypeName.getSimpleTypeName()+"Meta",
				0,
				DataType.TYPE,
				null,
				classArgs
		);
		return routeName;
	}

    ClassOrInterfaceTypeDetails getGovernor(
            final String metadataIdentificationString) {
        final JavaType governorTypeName = MaproModelProxyMetadata
                .getJavaType(metadataIdentificationString);
        final LogicalPath governorTypePath = MaproModelProxyMetadata
                .getPath(metadataIdentificationString);

        final String physicalTypeId = PhysicalTypeIdentifier.createIdentifier(
                governorTypeName, governorTypePath);
        return typeLocationService.getTypeDetails(physicalTypeId);
    }

    public String getProvidesType() {
        return MaproModelProxyMetadata.getMetadataIdentifierType();
    }

    public void notify(final String upstreamDependency,
            String downstreamDependency) {
    	
    	
        if (MetadataIdentificationUtils
                .isIdentifyingClass(downstreamDependency)) {
            Validate.isTrue(
                    MetadataIdentificationUtils.getMetadataClass(
                            upstreamDependency).equals(
                            MetadataIdentificationUtils
                                    .getMetadataClass(PhysicalTypeIdentifier
                                            .getMetadataIdentiferType())),
                    "Expected class-level notifications only for PhysicalTypeIdentifier (not '"
                            + upstreamDependency + "')");

            final ClassOrInterfaceTypeDetails cid = typeLocationService
                    .getTypeDetails(upstreamDependency);
            if (cid == null) {
                return;
            }
            if (MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(),
            		RooJavaType.ROO_GWT_PROXY) != null) {
                for (final ClassOrInterfaceTypeDetails proxyCid : typeLocationService
                        .findClassesOrInterfaceDetailsWithAnnotation(ROO_MAPRO_PROXY)) {
                    final JavaType typeName = PhysicalTypeIdentifier
                            .getJavaType(proxyCid
                                    .getDeclaredByMetadataId());
                    final LogicalPath typePath = PhysicalTypeIdentifier
                            .getPath(proxyCid
                                    .getDeclaredByMetadataId());
                    downstreamDependency = MaproModelProxyMetadata
                            .createIdentifier(typeName, typePath);
                    
                    doEvictAndGet(upstreamDependency,downstreamDependency);
                    
                }
                
                for (final ClassOrInterfaceTypeDetails proxyCid : typeLocationService
                		.findClassesOrInterfaceDetailsWithAnnotation(MaproModelJavaType.ROO_MAPRO_PROXY_ACCESSOR)) {
                	final JavaType typeName = PhysicalTypeIdentifier
                			.getJavaType(proxyCid
                					.getDeclaredByMetadataId());
                	final LogicalPath typePath = PhysicalTypeIdentifier
                			.getPath(proxyCid
                					.getDeclaredByMetadataId());
                	downstreamDependency = MaproModelProxyMetadata
                			.createIdentifier(typeName, typePath);
                	
                	doEvictAndGet(upstreamDependency,downstreamDependency);
                	
                }
                
            } else if (MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(),
            		ROO_MAPRO_PROXY) != null || MemberFindingUtils.getAnnotationOfType(cid.getAnnotations(),
                    		MaproModelJavaType.ROO_MAPRO_PROXY_ACCESSOR) != null) {
                // A physical Java type has changed, and determine what the
                // corresponding local metadata identification string would have
                // been
                final JavaType typeName = PhysicalTypeIdentifier
                        .getJavaType(upstreamDependency);
                final LogicalPath typePath = PhysicalTypeIdentifier
                        .getPath(upstreamDependency);
                downstreamDependency = MaproModelProxyMetadata.createIdentifier(
                        typeName, typePath);
            	doEvictAndGet(upstreamDependency,downstreamDependency);
            } else {
            	return;
            }

//            // We only need to proceed if the downstream dependency relationship
//            // is not already registered
//            // (if it's already registered, the event will be delivered directly
//            // later on)
//            if (metadataDependencyRegistry.getDownstream(upstreamDependency)
//                    .contains(downstreamDependency)) {
//                return;
//            }
        } else {
        	
        	doEvictAndGetDirect(downstreamDependency);
        }

    }

	private void doEvictAndGet(String upstreamDependency, String downstreamDependency) {
      // We only need to proceed if the downstream dependency relationship
      // is not already registered
      // (if it's already registered, the event will be delivered directly
      // later on)
      if (metadataDependencyRegistry.getDownstream(upstreamDependency)
              .contains(downstreamDependency)) {
          return;
      }		
		
		doEvictAndGetDirect(downstreamDependency);
	}

	private void doEvictAndGetDirect(String downstreamDependency) {
		// We should now have an instance-specific "downstream dependency" that
        // can be processed by this class
        Validate.isTrue(
                MetadataIdentificationUtils.getMetadataClass(
                        downstreamDependency).equals(
                        MetadataIdentificationUtils
                                .getMetadataClass(getProvidesType())),
                "Unexpected downstream notification for '"
                        + downstreamDependency
                        + "' to this provider (which uses '"
                        + getProvidesType() + "'");

        metadataService.evictAndGet(downstreamDependency);
	}

    public static boolean isAccessorMethod(final MethodMetadata method, ClassOrInterfaceTypeDetails declaredBy) {
        Validate.notNull(method, "Method is required");
        return (method.getMethodName().getSymbolName().startsWith("get") || method
                .getMethodName().getSymbolName().startsWith("is"))
                && method.getParameterTypes().isEmpty()
                && (declaredBy.getPhysicalTypeCategory()==PhysicalTypeCategory.INTERFACE || Modifier.isPublic(method.getModifier()));
    }

    public static boolean isMutatorMethod(final MethodMetadata method, ClassOrInterfaceTypeDetails declaredBy) {
        Validate.notNull(method, "Method is required");
        return method.getMethodName().getSymbolName().startsWith("set")
                && method.getParameterTypes().size() == 1
                && (declaredBy.getPhysicalTypeCategory()==PhysicalTypeCategory.INTERFACE || Modifier.isPublic(method.getModifier()));
    }
 
}
